﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Linq;
using Common.DataTypes;
using System.Globalization;


namespace LinqAmazonProvider
{
    public class AmazonExpressionVisitor 
    {
        private Dictionary<string, string> _searchParameters;
        private bool _isDescending;
        private string _methodMemberExpression; 
        public Dictionary<string,string> ProcessExpression(Expression expression)
        {
            _searchParameters = new Dictionary<string,string>();
            _searchParameters.Add("Method",null);
            VisitExpression(expression);
            return _searchParameters;
        }

        protected virtual void VisitExpression(Expression expression)
        {
            
            if (!(expression is BinaryExpression))
            {
                if (expression is MethodCallExpression)
                {
                    VisitMethodCall((MethodCallExpression)expression);
                }
                else if (expression is LambdaExpression)
                {
                    VisitExpression(((LambdaExpression)expression).Body);
                }
                else if (expression is MemberExpression)
                {
                    VisitMemberExpression((MemberExpression)expression, _isDescending);
                }
            }
            else
            {
                BinaryExpression binExp = (BinaryExpression)expression;

                if (expression.NodeType == ExpressionType.AndAlso)
                {
                    VisitAndAlso(binExp);
                }
                else if (expression.NodeType == ExpressionType.Equal)
                {
                    VisitEqual(binExp);
                }
                else if (expression.NodeType == ExpressionType.LessThanOrEqual)
                {
                    VisitLessThanOrEqual(binExp);
                }
                else if (expression.NodeType == ExpressionType.LessThan)
                {
                    VisitLessThan(binExp);
                }
                else if (expression.NodeType == ExpressionType.GreaterThanOrEqual)
                {
                    VisitGreaterThanOrEqual(binExp);
                }
                else if (expression.NodeType == ExpressionType.GreaterThan)
                {
                    VisitGreaterThan(binExp);
                }
            }
        }

        private void VisitMemberExpression(MemberExpression memberExpression, bool descending)
        {
            switch (_methodMemberExpression) {
                case "OrderBy":
                    string sortParameter = null;
                    int i = 1;
                    bool end = false;
                    string orderByKey = "Sort.";
                    switch (memberExpression.Member.Name)
                    {

                        case "Title":
                            sortParameter = "titlerank";
                            break;
                        case "ListPrice":
                            if (!descending)
                            {
                                sortParameter = "pricerank";
                            }
                            else
                            {
                                sortParameter = "inverse-pricerank";
                            }
                            break;
                        case "PublicationDate":
                            sortParameter = "daterank";
                            break;
                        default:
                            throw new NotSupportedException("Key to Order not supported");


                    }

                    while (!end)
                    {
                        if (_searchParameters.ContainsKey(orderByKey + i))
                        {
                            i++;
                        }
                        else
                        {
                            _searchParameters.Add(orderByKey + i, sortParameter);
                            end = true;
                        }
                    }

                    // Change sort order to get it back to normal
                    while (i > 1)
                    {
                        _searchParameters[orderByKey + i] = _searchParameters[orderByKey + (i - 1)];
                        i--;
                    }
                    _searchParameters[orderByKey + 1] = sortParameter;

                    descending = false;
                    break;
                case "Select":
                    _searchParameters["Select"] = memberExpression.Member.Name;
                    break;
            }
            
        }

        private void VisitAndAlso(BinaryExpression andAlso)
        {
            VisitExpression(andAlso.Left);
            VisitExpression(andAlso.Right);
        }

        private void VisitEqual(BinaryExpression expression)
        {
            string parameterName;
            string parameterValue;

            if (expression.Left.NodeType == ExpressionType.MemberAccess) 
            {
                parameterName = ((MemberExpression)expression.Left).Member.Name;

                if (expression.Right.NodeType == ExpressionType.Constant)
                {
                    parameterValue = (string)((ConstantExpression)expression.Right).Value;
                    //Add to the dictionary(criteria) one of the search parameters .
                    _searchParameters.Add(parameterName, parameterValue);
                }
                else if (expression.Right.NodeType == ExpressionType.MemberAccess)
                {
                    _searchParameters.Add(parameterName, GetMemberValue((MemberExpression)expression.Right).ToString());
                }
                else
                {
                    throw new NotSupportedException("Expression type not supported : " + expression.Right.NodeType.ToString());
                }
            }
        }

        private void VisitLessThanOrEqual(BinaryExpression expression)
        {
            string parameterName;
            string parameterValue;

            if (expression.Left.NodeType == ExpressionType.MemberAccess)
            {
                parameterName = "MaximumPrice";

                if (expression.Right.NodeType == ExpressionType.Constant)
                {
                    parameterValue = (((ConstantExpression)expression.Right).Value).ToString();
                    //Add to the dictionary(criteria) one of the search parameters .
                    _searchParameters.Add(parameterName, parameterValue);
                }
                else if (expression.Right.NodeType == ExpressionType.MemberAccess)
                {
                    _searchParameters.Add(parameterName, GetMemberValue((MemberExpression)expression.Right).ToString());
                }
                else
                {
                    throw new NotSupportedException("Expression type not supported : " + expression.Right.NodeType.ToString());
                }
            }
        }

        private void VisitLessThan(BinaryExpression expression)
        {
            string parameterName;
            string parameterValue;

            if (expression.Left.NodeType == ExpressionType.MemberAccess)
            {
                parameterName = "MaximumPrice";
                if (expression.Right.NodeType == ExpressionType.Constant)
                {
                    parameterValue = ((int)((ConstantExpression)expression.Right).Value - 1).ToString(CultureInfo.InvariantCulture);
                    //Add to the dictionary(criteria) one of the search parameters .
                    _searchParameters.Add(parameterName, parameterValue);
                }
                else if (expression.Right.NodeType == ExpressionType.MemberAccess)
                {
                    _searchParameters.Add(parameterName, GetMemberValue((MemberExpression)expression.Right).ToString());
                }
                else
                {
                    throw new NotSupportedException("Expression type not supported : " + expression.Right.NodeType.ToString());
                }
            }
        }

        private void VisitGreaterThanOrEqual(BinaryExpression expression)
        {
            string parameterName;
            string parameterValue;

            if (expression.Left.NodeType == ExpressionType.MemberAccess)
            {
                parameterName = "MinimumPrice";

                if (expression.Right.NodeType == ExpressionType.Constant)
                {
                    parameterValue = (((ConstantExpression)expression.Right).Value).ToString();
                    //Add to the dictionary(criteria) one of the search parameters .
                    _searchParameters.Add(parameterName, parameterValue);
                }
                else if (expression.Right.NodeType == ExpressionType.MemberAccess)
                {
                    _searchParameters.Add(parameterName, GetMemberValue((MemberExpression)expression.Right).ToString());
                }
                else
                {
                    throw new NotSupportedException("Expression type not supported : " + expression.Right.NodeType.ToString());
                }
            }
        }

        private void VisitGreaterThan(BinaryExpression expression)
        {
            string parameterName;
            string parameterValue;

            if (expression.Left.NodeType == ExpressionType.MemberAccess)
            {
                parameterName = "MinimumPrice";

                if (expression.Right.NodeType == ExpressionType.Constant)
                {
                    parameterValue = ((int)((ConstantExpression)expression.Right).Value + 1).ToString(CultureInfo.InvariantCulture);
                    //Add to the dictionary(criteria) one of the search parameters .
                    _searchParameters.Add(parameterName, parameterValue);
                }
                else if (expression.Right.NodeType == ExpressionType.MemberAccess)
                {
                    _searchParameters.Add(parameterName, GetMemberValue((MemberExpression)expression.Right).ToString());
                }
                else
                {
                    throw new NotSupportedException("Expression type not supported : " + expression.Right.NodeType.ToString());
                }
            }
        }


        private void VisitMethodCall(MethodCallExpression expression)
        {
            if((expression.Method.DeclaringType == typeof(Queryable)))
            {
                switch (expression.Method.Name)
	            {
		            case "Where":
                        VisitExpression(((UnaryExpression)expression.Arguments[1]).Operand);
                        break;
                    case "OrderBy":
                    case "ThenBy":
                        _methodMemberExpression = "OrderBy";
                        VisitExpression(((UnaryExpression)expression.Arguments[1]).Operand);
                        VisitExpression(expression.Arguments[0]);
                        break;
                    case "ThenByDescending":
                    case "OrderByDescending":
                        _methodMemberExpression = "OrderBy";
                        _isDescending = true;
                        VisitExpression(((UnaryExpression)expression.Arguments[1]).Operand);
                        VisitExpression(expression.Arguments[0]);  
                        break;
                    case "Count":
                    case "First":
                    case "Last":
                        _searchParameters["Method"]=expression.Method.Name;
                        VisitExpression(expression.Arguments[0]);  
                        break;

                    case "Skip":
                    case "Take":
                        _searchParameters.Add(expression.Method.Name, expression.Arguments[1].ToString());
                        VisitExpression(expression.Arguments[0]);
                        break;
                    case "Select":
                         _searchParameters["Select"] = "";
                        _methodMemberExpression = "Select";
                        VisitExpression(((UnaryExpression)expression.Arguments[1]).Operand);
                        VisitExpression(expression.Arguments[0]);
                        break;
                    default:
                    break;
	            }
            }
            else
            {
                if ((expression.Method.DeclaringType == typeof(String)) &&
              (expression.Method.Name == "Contains"))
                {
                    if (expression.Object.NodeType == ExpressionType.MemberAccess)
                    {
                        MemberExpression memberExpr = (MemberExpression)expression.Object;
                        if (memberExpr.Expression.Type == typeof(Book))
                        {
                            Expression argument;

                            argument = expression.Arguments[0];
                            if (argument.NodeType == ExpressionType.Constant)
                            {
                                _searchParameters.Add(memberExpr.Member.Name, (String)((ConstantExpression)argument).Value);
                            }
                            else if (argument.NodeType == ExpressionType.MemberAccess)
                            {
                                _searchParameters.Add(memberExpr.Member.Name, (String)GetMemberValue((MemberExpression)argument));
                            }
                            else
                            {
                                throw new NotSupportedException("Expression type not supported: " + argument.NodeType.ToString());
                            }
                        }
                    }
                }
                else
                {
                    throw new NotSupportedException("Method not supported: " + expression.Method.Name);
                }
            }
        }

        #region Helpers
        private static Object GetMemberValue(MemberExpression memberExpression)
        {
            LambdaExpression lambda = Expression.Lambda(memberExpression);
            Delegate fn = lambda.Compile();
           
            if (memberExpression == null)
                throw new ArgumentNullException("memberExpression");

            return fn.DynamicInvoke(null);
        }

        #endregion Helpers
    }
}
